package org.example.java.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {

  /*标识数字*/
  private int flag = 0;
  /*缓冲区大小*/
  private int BLOCK = 4096;
  /*接受数据缓冲区*/
  private ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK);
  /*发送数据缓冲区*/
  private ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);
  private Selector selector;
  /*服务器端地址*/
  private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress("localhost", 18888);


  public NIOServer() throws IOException {
    // 打开服务器套接字通道
    ServerSocketChannel channel = ServerSocketChannel.open();
    // 服务器配置为非阻塞
    channel.configureBlocking(false);
    // 检索与此通道关联的服务器套接字
    ServerSocket socket = channel.socket();
    // 进行服务的绑定
    socket.bind(SERVER_ADDRESS);
    // 通过open()方法找到Selector
    selector = Selector.open();
    // 注册到selector，等待连接
    channel.register(selector, SelectionKey.OP_ACCEPT);
    System.out.println("Server Start...");
  }


  // 监听
  private void listen() throws IOException {
    Set<SelectionKey> selectionKeys;
    Iterator<SelectionKey> iterator;
    SelectionKey key;
    while (true) {
      // 选择一组键，并且相应的通道已经打开
      selector.select();
      // 返回此选择器的已选择键集。
      selectionKeys = selector.selectedKeys();
      iterator = selectionKeys.iterator();
      while (iterator.hasNext()) {
        key = iterator.next();
        iterator.remove();
        // 测试此键的通道是否已准备好接受新的套接字连接。
        if (key.isAcceptable()) {
          doAccept(key);
        } else if (key.isReadable()) {
          doRead(key);
        } else if (key.isWritable()) {
          doWrite(key);
        }
      }
    }
  }

  private void doAccept(SelectionKey key) throws IOException {
    ServerSocketChannel server;
    SocketChannel client;// 返回为之创建此键的通道。
    server = (ServerSocketChannel) key.channel();
    // 接受到此通道套接字的连接。
    // 此方法返回的套接字通道（如果有）将处于阻塞模式。
    client = server.accept();
    // 配置为非阻塞
    client.configureBlocking(false);
    // 注册到selector，等待连接
    client.register(selector, SelectionKey.OP_READ);
  }

  private void doRead(SelectionKey key) throws IOException {
    SocketChannel client;
    int count;
    String receiveText;// 返回为之创建此键的通道。
    client = (SocketChannel) key.channel();
    //将缓冲区清空以备下次读取
    receiveBuffer.clear();
    //读取服务器发送来的数据到缓冲区中
    count = client.read(receiveBuffer);
    if (count > 0) {
      receiveText = new String(receiveBuffer.array(), 0, count);
      System.out.println("服务器端接受客户端数据:" + receiveText);
      client.register(selector, SelectionKey.OP_WRITE);
    }
  }

  private void doWrite(SelectionKey key) throws IOException {
    SocketChannel client;
    String sendText;//将缓冲区清空以备下次写入
    sendBuffer.clear();
    // 返回为之创建此键的通道。
    client = (SocketChannel) key.channel();
    sendText = "server[" + client.socket().getLocalAddress() + ":" + client.socket().getLocalPort() + "]:" + (flag++);
    //向缓冲区中输入数据
    sendBuffer.put(sendText.getBytes());
    //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
    sendBuffer.flip();
    //输出到通道
    client.write(sendBuffer);
    System.out.println("服务器端向客户端发送数据：" + sendText);
    client.register(selector, SelectionKey.OP_READ);
  }

  public static void main(String[] args) throws IOException {
    NIOServer server = new NIOServer();
    server.listen();
  }

}
